﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;

namespace VideoClipNameHelper
{
    //===========================================================================================================================
    /*
    public class ArtistItem
    {
        public string Item { get; set; }
        public bool IsItemArtist { get; set; }
        //
        public ArtistItem(string item, bool isItemArtist)
        {
            this.Item = item;
            this.IsItemArtist = isItemArtist;
        }
        public override string ToString()
        {
            return string.Format("Item:{{0}], IsItemArtist:{1}", Item, IsItemArtist);
        }
    }
    */
    //===========================================================================================================================
    // kanoniczna postać nazwy: Artist - [(99)] Title [(1999)] [(...)] [999x999[+XXXX]
    // musi posiadać:
    // separator ' - ' ( TERAZ już nie: lub ' + ')
    // inforamcję o rozdzielczości [999x999] lub rozszerzona o audio
    // data musi być w nawiasach
    // rozdzielczość musi być na końcu


    //private static readonly string rightNamePattern = @"^(.*?)(\s[-|+]\s)(.*)(\[\d{3,}[xX]\d{3,}.*\])$";
    //private static readonly string rightNamePattern = @"^(.*?)(\s[-|+]\s)(\(\d{2}\))*\s*(.*)(\[\d{3,}[xX]\d{3,}.*\])$";
    //private static readonly string rightNamePattern = @"^(.*?)(\s[-|+]\s)(\(\d{2}\))*\s*(.*)(\[.*?\])$";
    //private static readonly string rightNamePattern = @"^(.*?) - (\(\d{2}\))*\s*(.*)(\[.*?\])$";
    //
    [DataContract]
    public class VideoClipData
    {
        //
        public const string Separator = " - ";
        //
        public const int UndefinedOrdinal = -1;
        public const int UndefinedYear = 0;
        //
        [DataMember]
        public string OriginalName { get; set; }
        [DataMember]
        public string Artist { get; set; }
        [DataMember]
        //public List<ArtistItem> Artists { get; set; }
        public List<string> Artists { get; set; }
        [DataMember]
        public string Album { get; set; }
        [DataMember]
        public int Ordinal { get; set; }
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public List<string> AltTitles { get; set; }
        [DataMember]
        public List<string> TitleExtras { get; set; }
        [DataMember]
        public string Resolution { get; set; }
        [DataMember]
        public int Year { get; set; }
        //
        public VideoClipData()
        {
            OriginalName = "";
            Artist = "";
            Artists = new List<string>();
            Album = "";
            Ordinal = UndefinedOrdinal;
            Title = "";
            AltTitles = new List<string>();
            TitleExtras = new List<string>();
            Resolution = "";
            Year = UndefinedYear;
        }

        public VideoClipData(VideoClipData vfNew)
        {
            OriginalName = vfNew.OriginalName;
            Artist = vfNew.Artist;
            Artists = new List<string>(vfNew.Artists);
            Album = vfNew.Album;
            Ordinal = vfNew.Ordinal;
            Title = vfNew.Title;
            AltTitles = new List<string>(vfNew.AltTitles);
            TitleExtras = new List<string>(vfNew.TitleExtras);
            Resolution = vfNew.Resolution;
            Year = vfNew.Year;
        }

        public override string ToString()
        {
            if("" !=Album)
            {
                return string.Format($"Artist:[{Artist}], Album:[{Album}], Title:[{Title}], Resolution:[{Resolution}], Year:[{Year}]");
            }
            return string.Format($"Artist:[{Artist}], Title:[{Title}], Resolution:[{Resolution}], Year:[{Year}]");
        }

        /// <summary>
        /// Pathname zamienia na Filename bez rozdzielczości
        /// </summary>
        /// <param name="pathNameExt"></param>
        /// <returns></returns>
        public static string GetNameWithoutResolution(string pathNameExt)
        {
            VideoClipData vc = new VideoClipData();
            VideoClipData.PARSE_RESULT pr = VideoClipData.ParseWeak(System.IO.Path.GetFileNameWithoutExtension(pathNameExt), vc, false);
            if (pr != VideoClipNameHelper.VideoClipData.PARSE_RESULT.Invlid)
            {
                return vc.Concat(true);
            }
            return "";
        }
        /*
        public static string GetConcatFromFileName(string pathNameExt)
        {
            VideoClipData vc = new VideoClipData();
            VideoClipData.PARSE_RESULT pr = VideoClipData.ParseWeak(System.IO.Path.GetFileName(pathNameExt), vc, false);
            if (pr != VideoClipNameHelper.VideoClipData.PARSE_RESULT.Invlid)
            {
                return vc.Concat(true);
            }
            return "";
        }
        */
        public string Concat(bool isWithoutResolution=false)
        {
            string extras = "";
            if (TitleExtras.Count > 0)
            {
                extras = string.Join("", TitleExtras.ToArray());
            }
            string artists = Artist;
            //if (Artists.Count() > 1)
            //{
            //    //artists = string.Concat((from x in Artists select x.Item).ToArray());//.Join(" & ", Artists);
            //    artists =string.Join(" & ", Artists);
            //}
            string ordinal = "";
            if (Ordinal != UndefinedOrdinal)
            {
                ordinal = string.Format("({0:D2}) ", Ordinal);
            }
            //
            if ("" == Album)
            {
                if (isWithoutResolution) return artists.Trim() + " - " + ordinal + Title + extras;
                else return artists.Trim() + " - " + ordinal + Title + extras + Resolution;
            }
            else
            {
                if (isWithoutResolution) return artists.Trim() + "[" + Album + " - " + ordinal + Title + extras;
                else return artists.Trim() + "[" + Album + " - " + ordinal + Title + extras + Resolution;
            }
        }
        
        public static bool IsTitleExtrasEqual(VideoClipData vcd1, VideoClipData vcd2)
        {
            IEnumerable<string> inList1ButNotInList2 = vcd1.TitleExtras.Except(vcd2.TitleExtras);
            IEnumerable<string> inList2ButNotInList1 = vcd2.TitleExtras.Except(vcd1.TitleExtras);
            //
            return (0 == inList1ButNotInList2.Count()) && (0 == inList2ButNotInList1.Count());
        }

        public override int GetHashCode()
        {
            return (Artist + Album + Title + string.Concat(TitleExtras.ToArray()) + Resolution).ToLowerInvariant().GetHashCode();
        }

        /// <summary>
        /// Porównuje this z vcd: Artist i Title,
        /// opcjonalnie TitleExtras: this musi zawierać wszystkie z vcd
        /// </summary>
        /// <param name="vcd"></param>
        /// <returns></returns>
        public bool IsEqWeak(VideoClipData vcd)
        {
            if (0 != string.Compare(this.Artist, vcd.Artist, true)) return false;
            if (0 != string.Compare(this.Title, vcd.Title, true)) return false;
            //
            if(vcd.TitleExtras.Count > 0)
            {
                if (0 == this.TitleExtras.Count) return false;
                //
                foreach(string te in vcd.TitleExtras)
                {
                    if(null == this.TitleExtras.FirstOrDefault(x => 0 == string.Compare(x, te, true)))
                    {
                        return false;
                    }
                }
                //
            }
            //
            return true;
        }

        public static bool operator ==(VideoClipData vcd1, VideoClipData vcd2)
        {
            if (object.ReferenceEquals(null, vcd1) && object.ReferenceEquals(null, vcd2)) return true;
            if (object.ReferenceEquals(null, vcd1) || object.ReferenceEquals(null, vcd2)) return false;
            //
            return
            (vcd1.Ordinal == vcd2.Ordinal) &&
            (vcd1.Year == vcd2.Year) &&
            (vcd1.TitleExtras.Count == vcd2.TitleExtras.Count) &&
            (0 == string.Compare(vcd1.Artist, vcd2.Artist, true)) &&
            (0 == string.Compare(vcd1.Title, vcd2.Title, true)) &&
            (0 == string.Compare(vcd1.Album, vcd2.Album, true)) &&
            (0 == string.Compare(vcd1.Resolution, vcd2.Resolution, true)) &&
            IsTitleExtrasEqual(vcd1, vcd2)
            //(0 == string.Compare(string.Concat(vcd1.TitleExtras.ToArray()), string.Concat(vcd2.TitleExtras.ToArray()), true))
            ;
        }
        public static bool operator !=(VideoClipData vcd1, VideoClipData vcd2)
        {
            if (object.ReferenceEquals(null, vcd1) && object.ReferenceEquals(null, vcd2)) return false;
            if (object.ReferenceEquals(null, vcd1) || object.ReferenceEquals(null, vcd2)) return true;
            //
            return
            (vcd1.Ordinal != vcd2.Ordinal) ||
            (vcd1.Year != vcd2.Year) ||
            (vcd1.TitleExtras.Count != vcd2.TitleExtras.Count) ||
            (0 != string.Compare(vcd1.Artist, vcd2.Artist, true)) ||
            (0 != string.Compare(vcd1.Title, vcd2.Title, true)) ||
            (0 != string.Compare(vcd1.Album, vcd2.Album, true)) ||
            (0 != string.Compare(vcd1.Resolution, vcd2.Resolution, true)) ||
            !IsTitleExtrasEqual(vcd1, vcd2)
            //(0 != string.Compare(string.Concat(vcd1.TitleExtras.ToArray()), string.Concat(vcd2.TitleExtras.ToArray()), true))
            ;
        }


        public static bool IsNameOK(string fnWoPathExt)
        {
            VideoClipData vf = new VideoClipData();
            return "" == Parse(fnWoPathExt, vf);
        }

        /// <summary>
        /// Parsuje podaną nazwę pliku, wpisując składniki do vf
        /// </summary>
        /// <param name="fileNameWithExt">bez PATH, z EXT</param>
        /// <param name="vf"></param>
        /// <param name="isCheckResolution"></param>
        /// <returns>pusty string gdy OK, inaczej komunikat o błędzie</returns>
        public static string Parse(string fileNameWithExt, VideoClipData vf, bool isCheckResolution=true)
        {
            //
            vf.OriginalName= fileNameWithExt;
            vf.Artist = "";
            vf.Album = "";
            string ordinalTxt;
            vf.Ordinal = UndefinedOrdinal;
            vf.Title = "";
            vf.Resolution ="";
            vf.Year = UndefinedYear;
            List<string> extras = new List<string>();
            //
            int n = fileNameWithExt.IndexOf(Separator);
            if (n < 0)
            {
                return "01: brak separatora ` - `";
            }
            if (0 == n)
            {
                return "02: separatora ` - ` na początku";
            }
            //
            //---------------------------------------------------------------------
            // Left part
            //
            vf.Artist = fileNameWithExt.Substring(0, n);
            if ((vf.Artist.Length > 0) &&(' ' == vf.Artist[0])) vf.Artist = vf.Artist.TrimStart();
            if ((vf.Artist.Length > 0) && (' ' == vf.Artist[vf.Artist.Length - 1])) vf.Artist = vf.Artist.TrimEnd();
            //
            if (vf.Artist.Length < 1)
            {
                return "10: 'Artist' za krótki";
            }
            //
            vf.Album = "";
            vf.Title = fileNameWithExt.Substring(n + Separator.Length).TrimStart();
            if ((vf.Title.Length > 0) && (' ' == vf.Title[0])) vf.Title = vf.Title.TrimStart();
            if ((vf.Title.Length > 0) && (' ' == vf.Title[vf.Title.Length - 1])) vf.Title = vf.Title.TrimEnd();
            //
            //
            n = vf.Artist.IndexOf("[");
            if (n >= 0)
            {
                vf.Album = vf.Artist.Substring(n + 1);
                if ((vf.Album.Length > 0) && (' ' == vf.Album[0])) vf.Album = vf.Album.TrimStart();
                if ((vf.Album.Length > 0) && (' ' == vf.Album[vf.Album.Length - 1])) vf.Album = vf.Album.TrimEnd();
                if (vf.Album.Length < 1)
                {
                    return "12: 'Album' za krótki";
                }

                //
                vf.Artist = vf.Artist.Substring(0, n);
                if ((vf.Artist.Length > 0) && (' ' == vf.Artist[0])) vf.Artist = vf.Artist.TrimStart();
                if ((vf.Artist.Length > 0) && (' ' == vf.Artist[vf.Artist.Length - 1])) vf.Artist = vf.Artist.TrimEnd();
                //
                if (vf.Artist.Length < 1)
                {
                    return "14: 'Artist' za krótki";
                }
            }
            //
            vf.Artist = vf.Artist;// Weak
            //
            //---------------------------------------------------------------------
            // Right part
            //
            // tytuł może zaczynać się od czegoś w nawiasach
            // więc, jeżeli na poczatku to nie Ordinal, to znaczy, ze to część tytułu
            if (('(' == vf.Title[0]) && (vf.Title.Length >= 5) && checkOrdinal(vf.Title))
            {
                // = -1
                ordinalTxt = vf.Title.Substring(0, 4);
                vf.Ordinal = int.Parse(ordinalTxt.Substring(1, 2));
                //
                vf.Title = vf.Title.Substring(4);
                if ((vf.Title.Length > 0) && (' ' == vf.Title[0])) vf.Title = vf.Title.TrimStart();
                if ((vf.Title.Length > 0) && (' ' == vf.Title[vf.Title.Length - 1])) vf.Title = vf.Title.TrimEnd();
                //
                if (vf.Title.Length < 1)
                {
                    return "52: 'Title' za krótki";
                }
                //
            }
            //
            n = vf.Title.LastIndexOf("[");
            if (n < 0)
            {
                if(!isCheckResolution)
                {
                    goto skip_resolution_check;
                }
                //vf.Title = vf.Title;// Weak
                return "80: brak 'Resolution'";
            }
            int nEnd = vf.Title.IndexOf("]", n + 1);
            if(nEnd<0)
            {
                if (!isCheckResolution)
                {
                    goto skip_resolution_check;
                }
                return "81: brak w 'Resolution' ]";
            }
            vf.Resolution  = vf.Title.Substring(n, nEnd - n+1);
            if ((vf.Resolution .Length > 0) && (' ' == vf.Resolution [0])) vf.Resolution  = vf.Resolution .TrimStart();
            if ((vf.Resolution .Length > 0) && (' ' == vf.Resolution [vf.Resolution .Length - 1])) vf.Resolution  = vf.Resolution .TrimEnd();
            if (!checkResolution(vf.Resolution ))
            {
                return "82: 'Resolution' zły format";
            }
            //
            vf.Title = vf.Title.Substring(0, n);
            if ((vf.Title.Length > 0) && (' ' == vf.Title[0])) vf.Title = vf.Title.TrimStart();
            if ((vf.Title.Length > 0) && (' ' == vf.Title[vf.Title.Length - 1])) vf.Title = vf.Title.TrimEnd();
            if (vf.Title.Length < 1)
            {
                return "84: 'Title' za krótki";
            }
            skip_resolution_check:;
            //
            //vf.Title = vf.Title;// Weak
            //
            //---------------------------------------------------------------------
            //
            if(!parseArtist(vf.Artist, vf.Artists))
            {
                return "92: błąd w 'parseArtist'";
            }
            //
            if('('==vf.Title[0])
            {
                // Tytuł rozpoczyna się od (..), które jest częścią tytułu
                // odcinamy całość, aż do pierwszego extras
                int i = vf.Title.LastIndexOf('(');
                if(i>0)
                {
                    string titleExtras = vf.Title.Substring(i);
                    vf.Title = vf.Title.Substring(0, i);
                    string titleDummy;
                    int year;
                    if (!parseTitleExtras(titleExtras, out titleDummy, out year, vf.TitleExtras, vf.AltTitles))
                    {
                        return "94: błąd w 'parseTitleExtras'";
                    }
                    vf.Year = year;
                }
            }
            else
            {
                string titleSrc = vf.Title;
                string title;
                int year;
                if (!parseTitleExtras(titleSrc, out title, out year, vf.TitleExtras, vf.AltTitles))
                {
                    return "96: błąd w 'parseTitleExtras'";
                }
                vf.Title = title;
                vf.Year = year;
            }
            //
            //
            //
            //
            //vf.Artist = vf.Artist;
            //vf.Album = vf.Album;
            //vf.Ordinal = vf.Ordinal;
            //vf.Title = vf.Title;
            //vf.Resolution = vf.Resolution ;
            //vf.Year = vf.Year;
            //
            //
            return "";
        }

        public enum PARSE_RESULT { Full, Weak, Invlid }
        /// <summary>
        /// Parsuje fileNameWithExt do vf, dopuszczając parsowanie niepełnych danych
        /// </summary>
        /// <param name="fileNameWithExt">bez PATH, z EXT</param>
        /// <param name="vf"></param>
        /// <param name="isCheckResolution"></param>
        /// <returns>Full, Weak, Invlid</returns>
        public static PARSE_RESULT ParseWeak(string fileNameWithExt, VideoClipData vf, bool isCheckResolution = true)
        {
            string err = Parse(fileNameWithExt, vf, true);
            if(""==err)
            {
                return PARSE_RESULT.Full;
            }
            else
            {
                Parse(fileNameWithExt, vf, false);
                if ("" != vf.Artist && "" != vf.Title)
                {
                    return PARSE_RESULT.Weak;
                }

            }
            return PARSE_RESULT.Invlid;
        }
        public readonly static string[] Splittters = { " & ", " ft ", " ft. ", " featuring ", " feat ", " feat. ", " ft ", " ft. ", " f. ", " versus ", " vs ", " vs. " };

        //featuring, with, or and. It is abbreviated in credit lists as Feat., Ft., f/, f.

        public static bool parseArtist(string Artist, List<string> Artists)
        {
            //Artists = new List<ArtistItem>(); błąd, błąd...
            List<string> tokens = new List<string>();
            int previousBegin = 0;
            int k = 0;
            for (k = 0; k < Artist.Length; k++)
            {
                if (' ' == Artist[k])
                {
                    foreach (string splitter in Splittters)
                    {
                        if (0 == string.Compare(Artist, k, splitter, 0, splitter.Length, true))
                        {
                            //tokens.Add(new ArtistItem(Artist.Substring(previousBegin, k - previousBegin), true));
                            tokens.Add(Artist.Substring(previousBegin, k - previousBegin));
                            //
                            //tokens.Add(new ArtistItem(splitter, false));
                            //
                            //tokens.Add(Artist.Substring(previousBegin, k - previousBegin));
                            previousBegin = k + splitter.Length;
                            k += splitter.Length;
                            break;
                        }
                    }

                }

            }
            if (k >= Artist.Length && previousBegin < (Artist.Length))
            {
                tokens.Add(Artist.Substring(previousBegin));
                //tokens.Add(Artist.Substring(previousBegin));
            }


            if (tokens.Count > 1)
            {
                Artists.AddRange(tokens);
                return true;
            }
            return true;
        }

        private static bool parseTitleExtras(string title, out string newTitle, out int year, List<string> extras, List<string>altTitles)
        {
            newTitle = title;
            year = UndefinedYear;
            //
            if (-1 == title.IndexOf('('))
            {
                return true;
            }
            //
            for (;;)
            {
                int pOPen = newTitle.IndexOf("(");
                if (-1 == pOPen) break;
                int pClose = newTitle.IndexOf(")", pOPen);
                if (-1 == pClose) break;
                //
                //if(p)
                //
                string inParen = newTitle.Substring(pOPen, (pClose - pOPen) + 1);
                if (checkYear(inParen))
                {
                    year = int.Parse(inParen.Substring(1, 4));
                }
                else if (inParen.StartsWith("(=") && inParen.Length >4)
                {
                    altTitles.Add(inParen.Substring(2, inParen.Length - 3));
                }
                //
                extras.Add(inParen);
                //
                string leftPart = (0 == pOPen) ? "" : newTitle.Substring(0, pOPen).TrimEnd();
                string rightPart = (pClose == newTitle.Length - 1) ? "" : newTitle.Substring(pClose + 1, (newTitle.Length) - pClose - 1).Trim();
                //
                if ("" != rightPart)
                {
                    newTitle = leftPart + " " + rightPart;
                }
                else
                {
                    newTitle = leftPart;
                }
            }
            //
            return true;
        }

        private static bool checkOrdinal(string s)
        {
            // (88)
            if ((s.Length >= 4) && ('(' == s[0]) && Char.IsDigit(s[1]) && Char.IsDigit(s[2]) && (')' == s[3])) return true;
            return false;
        }
        private static bool checkResolution(string s)
        {
            // [624x464+102MP3]
            if ('[' != s[0] || ']' != s[s.Length - 1]) return false;
            int nX = s.IndexOf('x');
            int nPlus = s.IndexOf('+');
            //
            if (nX <= 0) return false;
            //
            if (nPlus >= 0)
            {
                if (nX > nPlus) return false;
                if (s.Length < 7) return false;
                //
                if (1 == nPlus) return false;
                if ((s.Length - 2) == nPlus) return false;
                if ((nX + 1) == nPlus) return false;

            }
            //
            if (s.Length < 5) return false;
            if (1 == nX) return false;
            if ((s.Length - 2) == nX) return false;
            //
            return true;
        }
        private static bool checkYear(string s)
        {
            // (1999)
            if ((s.Length == 6) && ('(' == s[0])
                && Char.IsDigit(s[1]) && Char.IsDigit(s[2])
                && Char.IsDigit(s[3]) && Char.IsDigit(s[4])
                && ('1' == s[1] || '2' == s[1])
                && ('9' == s[2] || '0' == s[2])
                && (')' == s[5]))
                return true;
            return false;
        }
    }//class
    //===========================================================================================================================
}//namespace
